home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 3794 / 3794.xpi / components / facebook.js next >
Text File  |  2009-11-16  |  54KB  |  1,239 lines

  1. /**
  2.  *
  3.  * The source code included in this file is licensed to you by Facebook under
  4.  * the Apache License, Version 2.0.  Accordingly, the following notice
  5.  * applies to the source code included in this file:
  6.  *
  7.  * Copyright ⌐ 2009 Facebook, Inc.
  8.  *
  9.  * Licensed under the Apache License, Version 2.0 (the "License"); you may
  10.  * not use this file except in compliance with the License. You may obtain
  11.  * a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  15.  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  16.  * License for the specific language governing permissions and limitations
  17.  * under the License.
  18.  *
  19.  */
  20.  
  21. const BASE_CHECK_INTERVAL = 5*60*1000; // 5 minutes
  22. const DEBUG     = false;
  23. const VERBOSITY = 0; // 0: no dumping, 1: normal dumping, 2: massive dumping
  24.  
  25. var debug = ( VERBOSITY < 1 )
  26.   ? function() {}
  27.   : function() {
  28.   dump('FacebookService: ');
  29.   if (debug.caller && debug.caller.name) {
  30.     dump(debug.caller.name + ': ');
  31.   }
  32.   for( var i=0; i < arguments.length; i++ ) {
  33.     if( i ) dump( ', ' );
  34.     switch( typeof arguments[i] ) {
  35.       case 'xml':
  36.         dump( arguments[i].toXMLString() );
  37.         break;
  38.       case 'object':
  39.         try { // won't work if object has methods :(
  40.             var out = JSON.stringify(arguments[i]);
  41.             dump(out);
  42.         } catch (e) {
  43.             dump( '[obj]\n' );
  44.             for( prop in arguments[i] )
  45.                 dump( ' ' + prop + ': ' + arguments[i][prop] + '\n' );
  46.             dump( '[/obj]\n' );
  47.         }
  48.         break;
  49.       default:
  50.         dump( arguments[i] );
  51.     }
  52.   }
  53.   dump('\n');
  54. };
  55. var vdebug = ( VERBOSITY < 2 ) ? function() {} : debug;
  56.  
  57. const CONTRACT_ID  = '@facebook.com/facebook-service;1';
  58. const CLASS_ID     = Components.ID('{e983db0e-05fc-46e7-9fba-a22041c894ac}');
  59. const CLASS_NAME   = 'Facebook API Connector';
  60.  
  61. const Cc = Components.classes;
  62. const Ci = Components.interfaces;
  63. const PASSWORD_URL = 'chrome://facebook/';
  64.  
  65. // Load MD5 code...
  66. Cc['@mozilla.org/moz/jssubscript-loader;1']
  67.     .getService(Ci.mozIJSSubScriptLoader)
  68.     .loadSubScript('chrome://facebook/content/md5.js');
  69.  
  70. // Compatibility with Firefox 3.0 that doesn't have native JSON.
  71. if (typeof(JSON) == "undefined") {
  72.   Components.utils.import("resource://gre/modules/JSON.jsm");
  73.   JSON.parse = JSON.fromString;
  74.   JSON.stringify = JSON.toString;
  75. }
  76.  
  77. /** class SetNotif:
  78.  * Encapsulates notifs for a set of ids delivered as a JSON array.
  79.  * Watcher for "size" property notifies the observer when the size value
  80.  * changes.
  81.  */
  82. function SetNotif( idArr, topic, dispatcher, on_new_item ) {
  83.     this.topic = topic;
  84.     this.dispatcher  = dispatcher;
  85.     this.on_new_item = on_new_item;
  86.     this.watch( "size", function( prop, oldVal, newVal ) {
  87.         if( oldVal != newVal )
  88.             dispatcher.notify( null, topic, newVal );
  89.         return newVal;
  90.     });
  91.     this.init( idArr );
  92. }
  93. SetNotif.prototype.__defineGetter__( "count", function() {
  94.   debug( this.topic, "count accessed", this.size );
  95.   return this.size;
  96. });
  97. SetNotif.prototype.update = function( idArr ) {
  98.     debug( "SetNotif.update", this.topic, idArr );
  99.     var itemSet = {};
  100.     var diff  = [];
  101.     this.size = idArr.length !== undefined ? idArr.length : 0;
  102.     for( var i=0; i<this.size; i++ ){
  103.         it = Number(idArr[i]);
  104.         itemSet[it] = true;
  105.         if( !this.items[it] )
  106.             diff.push(it);
  107.     }
  108.     if( diff.length > 0 && null != this.on_new_item )
  109.         this.on_new_item( this, diff );
  110.     this.items = itemSet;
  111. }
  112. SetNotif.prototype.init = function( idArr ) {
  113.     debug( "SetNotif.init", idArr );
  114.     this.size   = idArr.length !== undefined ? idArr.length : 0;
  115.     var itemSet = {};
  116.     if( this.size > 0 )
  117.         for each( var it in idArr )
  118.             itemSet[it] = true;
  119.     this.items = itemSet;
  120. }
  121.  
  122. /* class CountedNotif:
  123.  * Encapsulates notifs for which a JS object
  124.  * containing an unread and most recent element is present.
  125.  */
  126. function CountedNotif( notif, topic, dispatcher, on_new_unread ) {
  127.     this.topic = topic;
  128.     this.on_new_unread = on_new_unread;
  129.     this.dispatcher = dispatcher;
  130.     this.time  = Number(notif.most_recent);
  131.     this.count = Number(notif.unread);
  132. }
  133. CountedNotif.prototype.__defineSetter__( "count", function( count ) {
  134.   debug( this.topic, 'setCount', count );
  135.   this.dispatcher.notify(null, this.topic, count);
  136.   this._count = count;
  137. });
  138. CountedNotif.prototype.__defineGetter__( "count", function() {
  139.   debug( this.topic, "count accessed", this._count );
  140.   return this._count;
  141. });
  142. CountedNotif.prototype.setTime = function( new_time ) {
  143.     debug( this.topic, 'setTime', this.time, new_time );
  144.     if( ('function' == typeof this.on_new_unread)
  145.         && (new_time > this.time)
  146.         && (this.count > 0) ) {
  147.         this.on_new_unread( this.count );
  148.     }
  149.     if( new_time != this.time )
  150.         this.time = new_time;
  151. };
  152. CountedNotif.prototype.update = function(notif) {
  153.     this.count = Number(notif.unread);
  154.     this.setTime( Number(notif.most_recent) );
  155. };
  156.  
  157. var fbSvc; // so that all our callback functions objects can access "this"
  158. function facebookService() {
  159.     // wrappedJSObject for accessing properties directly from JavaScript.
  160.     // Used insetad of .idl when it would make it difficult or very verbose.
  161.     this.wrappedJSObject = this;
  162.  
  163.     debug('constructor');
  164.  
  165.     this._apiKey = '8d7be0a45c164647647602a27106cc65';
  166.     this._secret = 'c9646e8dccec4c2726c65f6f5eeca86a';
  167.  
  168.     this.stringBundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
  169.                               .getService(Components.interfaces.nsIStringBundleService)
  170.                               .createBundle("chrome://facebook/locale/facebook.properties");
  171.  
  172.     this.initValues();
  173.  
  174.     fbSvc = this;
  175.     if( !DEBUG )
  176.       this._checker = {
  177.         notify: function(timer) {
  178.           var now = Date.now();
  179.           // only do a check if either:
  180.           //   1. we loaded an fb page in the last minute
  181.           if ((fbSvc._lastFBLoad > fbSvc._lastChecked)
  182.           //   2. or we haven't checked in the last 5 minutes and any page has loaded
  183.               || ( fbSvc._lastPageLoad > fbSvc._lastChecked
  184.                   && now > fbSvc._lastChecked + BASE_CHECK_INTERVAL)
  185.           //   3. or we haven't checked in the last 10 minutes and no page has loaded
  186.               || ( now > fbSvc._lastChecked + BASE_CHECK_INTERVAL*2))
  187.           {
  188.             var now = Date.now();
  189.             var interval = now - fbSvc._lastChecked;
  190.             fbSvc._lastChecked = now;
  191.             debug('_checker.notify: checking', now, fbSvc._lastFBLoad, fbSvc._lastPageLoad, fbSvc._lastChecked);
  192.             // note: suppress notifications if we haven't successfully checked for the last 30 minutes
  193.             fbSvc.checkUsers(now > (fbSvc._lastCheckedFriends + BASE_CHECK_INTERVAL * 6));
  194.             fbSvc.checkNotifications(false, interval);
  195.             fbSvc.checkAlbums(interval);
  196.             fbSvc.checkCanSetStatus();
  197.           } else {
  198.             debug('_checker.notify: skipping', now, fbSvc._lastFBLoad, fbSvc._lastPageLoad, fbSvc._lastChecked);
  199.           }
  200.         }
  201.       };
  202.     else
  203.       this._checker = {
  204.         notify: function(timer) {
  205.           var now = Date.now();
  206.           var interval = now - fbSvc._lastChecked;
  207.           fbSvc._lastChecked = now;
  208.           debug('_checker.notify: checking', now, fbSvc._lastFBLoad, fbSvc._lastPageLoad, fbSvc._lastChecked);
  209.           // note: suppress notifications if we haven't successfully checked for the last 30 minutes
  210.           fbSvc.checkUsers(now > fbSvc._lastCheckedFriends + BASE_CHECK_INTERVAL * 6);
  211.           fbSvc.checkNotifications(false, interval);
  212.           fbSvc.checkAlbums(interval);
  213.           fbSvc.checkCanSetStatus();
  214.         }
  215.       };
  216.     this._initialize = {
  217.         notify: function(timer) {
  218.             debug('_initialize.notify');
  219.             fbSvc._lastChecked = Date.now();
  220.             fbSvc.checkUsers(true);
  221.             fbSvc.checkNotifications(true);
  222.             fbSvc.checkAlbums(0);
  223.             fbSvc.checkCanSetStatus();
  224.             fbSvc._dailyNotifier.set(timer);
  225.         }
  226.     };
  227.     this._dailyNotifier = {
  228.         // this is our really lame way of making sure that the status update
  229.         // times properly get updated each day (so that "today" becomes
  230.         // "yesterday", etc.).
  231.         set: function(timer) {
  232.             // note that we could use a repeating timer instead of always
  233.             // firing one shot timers, but this is slightly less code since we
  234.             // have to do it this way the first time around anyway, and since
  235.             // this only gets run once a day it seems harmless
  236.             var now = new Date();
  237.             var midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate()+1, 0, 0, 1);
  238.             timer.initWithCallback(this, midnight-now, Ci.nsITimer.TYPE_ONE_SHOT);
  239.         },
  240.         notify: function(timer) {
  241.             debug('_dailyNotifier.notify');
  242.             fbSvc.notify(null, 'facebook-new-day', null);
  243.             this.set(timer);
  244.         }
  245.     };
  246.     this._numAlertsObj = { value: 0 };
  247.  
  248.     this._winService      = Cc["@mozilla.org/appshell/window-mediator;1"]
  249.         .getService(Ci.nsIWindowMediator);
  250.     this._observerService = Cc["@mozilla.org/observer-service;1"]
  251.         .getService(Ci.nsIObserverService);
  252.     this._prefService     = Cc['@mozilla.org/preferences-service;1']
  253.         .getService(Ci.nsIPrefBranch2);
  254.     try {
  255.       this._alertService = Cc["@mozilla.org/alerts-service;1"]
  256.         .getService(Ci.nsIAlertsService);
  257.     } catch(e) {
  258.       this._alertService = null;
  259.     }
  260.  
  261.     this._ff3Login = false;
  262.     if ("@mozilla.org/passwordmanager;1" in Cc) {
  263.       // Password Manager exists so this is not Firefox 3 (could be Firefox 2, Netscape, SeaMonkey, etc).
  264.       this._pwdService      = Cc['@mozilla.org/passwordmanager;1'].getService(Ci.nsIPasswordManager);
  265.       this._pwdServiceInt   = Cc['@mozilla.org/passwordmanager;1'].getService(Ci.nsIPasswordManagerInternal);
  266.     } else if ("@mozilla.org/login-manager;1" in Cc) {
  267.       // Login Manager exists so this is Firefox 3
  268.       this._ff3Login = true;
  269.       this._loginManager = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
  270.     }
  271.  
  272.     this._observerService.addObserver(this, "final-ui-startup", false);
  273. }
  274.  
  275. function AlertObserver() { }
  276. AlertObserver.prototype = { // see https://developer.mozilla.org/en/nsIAlertsService
  277.     // Components.interfaces.nsIObserver
  278.     observe: function(subject, topic, data) {
  279.         vdebug("alert observed:", subject, "topic: " + topic, "data: " + data);
  280.         if (topic == 'alertclickcallback') {
  281.             debug('opening alert url', data);
  282.             var win = fbSvc._winService.getMostRecentWindow( "navigator:browser" );
  283.             var browser = win ? win.getBrowser() : null;
  284.             if( browser
  285.                 && 2 != fbSvc._prefService.getIntPref('browser.link.open_newwindow') )
  286.                 // 1 => current Firefox window;
  287.                 // 2 => new window;
  288.                 // 3 => a new tab in the current window;
  289.                 { // open in a focused tab
  290.                     var tab = browser.addTab( data );
  291.                     browser.selectedTab = tab;
  292.                     win.content.focus();
  293.                 }
  294.             else {
  295.                 win = Cc["@mozilla.org/appshell/appShellService;1"]
  296.                     .getService(Ci.nsIAppShellService).hiddenDOMWindow;
  297.                 win.open( data );
  298.             }
  299.         }
  300.     },
  301.  
  302.     // Components.interfaces.nsISupports
  303.     QueryInterface : function(iid) {
  304.         if ( iid.equals(Components.interfaces.nsIObserver)
  305.              || iid.equals(Components.interfaces.nsISupportsWeakReference)
  306.              || iid.equals(Components.interfaces.nsISupports)
  307.              )
  308.             return this;
  309.         throw Components.results.NS_NOINTERFACE;
  310.     }
  311. };
  312.  
  313. facebookService.prototype = {
  314.     // nsISupports implementation
  315.     QueryInterface: function (iid) {
  316.         if (iid.equals(Ci.fbIFacebookService) ||
  317.             iid.equals(Ci.nsIObserver) ||
  318.             iid.equals(Ci.nsISupports) ||
  319.             iid.equals(Ci.nsISupportsWeakReference) ||
  320.             iid.equals(Ci.nsIWeakReference)
  321.             ) {
  322.             return this;
  323.         }
  324.         throw Components.results.NS_ERROR_NO_INTERFACE;
  325.     },
  326.  
  327.     // nsIWeakReference
  328.     QueryReferent: function(iid) {
  329.         return this.QueryInterface(iid);
  330.     },
  331.  
  332.     // nsISupportsWeakReference
  333.     GetWeakReference: function() {
  334.         return this;
  335.     },
  336.  
  337.     // nsIObserver
  338.     observe: function(subject, topic, data) {
  339.         if (topic != "final-ui-startup")
  340.             return;
  341.         this.migrate();
  342.     },
  343.  
  344.     // ----------- Migration code -----------------//
  345.  
  346.     // Make the photo uploader button visible if the toolbar was customized.
  347.     migrate_0to1: function() {
  348.         const PHOTOUPLOAD_BUTTON_ID = "facebook-photoupload";
  349.         var currentSet = this._rdf.GetResource("currentset");
  350.  
  351.         // get an nsIRDFResource for the facebook-toolbar item
  352.         var fbBar = this._rdf.GetResource("chrome://browser/content/browser.xul#facebook-toolbar");
  353.         var target = this._getPersist(fbBar, currentSet);
  354.  
  355.         if (!target || target.indexOf(PHOTOUPLOAD_BUTTON_ID) != -1)
  356.             return;
  357.  
  358.         if (target.indexOf("facebook-share,") != -1) {
  359.             // Try to add it on the right of the share icon.
  360.             target = target.replace("facebook-share,", "facebook-share," + PHOTOUPLOAD_BUTTON_ID + ",");
  361.         } else if (target.indexOf(",spring,facebook-login-info") != -1) {
  362.             // Otherwise, try to add it on the left of the login info button.
  363.             target = target.replace(",spring,facebook-login-info",
  364.                                     "," + PHOTOUPLOAD_BUTTON_ID + ",spring,facebook-login-info");
  365.         } else {
  366.             // At last resort, put it in the end.
  367.             target += "," + PHOTOUPLOAD_BUTTON_ID;
  368.         }
  369.         this._setPersist(fbBar, currentSet, target);
  370.  
  371.         // force the RDF to be saved
  372.         if (this._dirty)
  373.             this._dataSource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
  374.     },
  375.  
  376.     migrate: function() {
  377.         const MIGRATION_PREF = "extensions.facebook.migration.version";
  378.         const LAST_MIGRATION_VERSION = 1;
  379.  
  380.         var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
  381.         var migration = 0;
  382.         try {
  383.             migration = prefBranch.getIntPref(MIGRATION_PREF);
  384.         } catch(ex) { }
  385.  
  386.         if (migration == LAST_MIGRATION_VERSION)
  387.             return;
  388.  
  389.         // grab the localstore.rdf and make changes needed for new UI
  390.         this._rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
  391.         this._dataSource = this._rdf.GetDataSource("rdf:local-store");
  392.         this._dirty = false;
  393.  
  394.         // Version 0 is for version less or equal to 1.3 (the migration.version
  395.         // pref didn't exist yet).
  396.  
  397.         // Version 1 is for version 1.4.
  398.         // This version adds the the photo uploader button which needs to be
  399.         // added if the toolbar was customized otherwise it will be hidden.
  400.  
  401.         if (migration == 0) {
  402.             this.migrate_0to1();
  403.         }
  404.  
  405.         // update the migration version
  406.         prefBranch.setIntPref(MIGRATION_PREF, LAST_MIGRATION_VERSION);
  407.         // free up the RDF service
  408.         this._rdf = null;
  409.         this._dataSource = null;
  410.     },
  411.  
  412.     _getPersist: function (aSource, aProperty) {
  413.         var target = this._dataSource.GetTarget(aSource, aProperty, true);
  414.         if (target instanceof Ci.nsIRDFLiteral)
  415.             return target.Value;
  416.         return null;
  417.     },
  418.  
  419.     _setPersist: function (aSource, aProperty, aTarget) {
  420.         this._dirty = true;
  421.         try {
  422.             var oldTarget = this._dataSource.GetTarget(aSource, aProperty, true);
  423.             if (oldTarget) {
  424.                 if (aTarget)
  425.                     this._dataSource.Change(aSource, aProperty, oldTarget, this._rdf.GetLiteral(aTarget));
  426.                 else
  427.                     this._dataSource.Unassert(aSource, aProperty, oldTarget);
  428.             }
  429.             else {
  430.                 this._dataSource.Assert(aSource, aProperty, this._rdf.GetLiteral(aTarget), true);
  431.             }
  432.         }
  433.         catch(ex) {}
  434.     },
  435.  
  436.     // ----------- Start Notifications -----------------//
  437.     get numMsgs()       { return this._messages.count; },
  438.     get numPokes()      { return this._pokes.count; },
  439.     get numReqs()       { return this._reqs.count; },
  440.     get numEventInvs()  { return this._eventInvs.count; },
  441.     get numGroupInvs()  { return this._groupInvs.count; },
  442.     // ----------- End Notifications -----------------//
  443.  
  444.     get apiKey() {
  445.         return this._apiKey;
  446.     },
  447.     get secret() {
  448.         return this._secret;
  449.     },
  450.     get loggedIn() {
  451.         return this._loggedIn;
  452.     },
  453.     get loggedInUser() {
  454.         return this._loggedInUser;
  455.     },
  456.     get canSetStatus() {
  457.         debug("Can Set Status", this._canSetStatus);
  458.         return Boolean(this._canSetStatus);
  459.     },
  460.     savedSessionStart: function() {
  461.         var uid = this._prefService.getCharPref('extensions.facebook.uid');
  462.         if (!uid) {return;}
  463.         debug( 'SAVED SESSION', uid );
  464.  
  465.         if (this._ff3Login) {
  466.           var hostname = PASSWORD_URL;
  467.           var formSubmitURL = PASSWORD_URL;
  468.           var session_secret = null,
  469.               session_key = null;
  470.  
  471.           // Find users for the given parameters
  472.           var logins = this._loginManager.findLogins({}, hostname, formSubmitURL, null);
  473.  
  474.           // Find user from returned array of nsILoginInfo objects
  475.           for (var i = 0; i < logins.length; i++) {
  476.               session_key    = logins[i].username;
  477.               session_secret = logins[i].password;
  478.               break;
  479.           }
  480.           this.sessionStart(session_key, session_secret, uid, true);
  481.         } else {
  482.           var session_secret = { value: "" },
  483.               session_key    = { value: "" },
  484.               throwaway      = { value: "" };
  485.  
  486.           this._pwdServiceInt.findPasswordEntry( PASSWORD_URL, null /* username */, null /* password */,
  487.               throwaway /* hostURIFound */, session_key /* usernameFound */, session_secret /*pwdFound*/ );
  488.           this.sessionStart( session_key.value, session_secret.value, uid, true );
  489.         }
  490.     },
  491.     sessionStart: function(sessionKey, sessionSecret, uid, saved) {
  492.         debug( 'sessionStart', sessionKey, sessionSecret, uid );
  493.         if (!sessionKey || !sessionSecret || !uid) {
  494.           debug('sessionStart called with invalid values, aborting');
  495.           if (saved) {this.sessionEnd();}
  496.           return;
  497.         }
  498.         this._sessionKey    = sessionKey;
  499.         this._sessionSecret = sessionSecret;
  500.         this._loggedIn      = true;
  501.         this._uid           = uid;
  502.  
  503.         if( !saved ) {
  504.           // persist API sessions across the Firefox shutdown
  505.           // by saving them in the password store
  506.           this.savePref( 'extensions.facebook.uid', this._uid );
  507.           if (this._ff3Login) {
  508.             var hostname = PASSWORD_URL;
  509.             var formSubmitURL = PASSWORD_URL;
  510.  
  511.             // Clear out saved information for this extension
  512.             var logins = this._loginManager.findLogins({}, hostname, formSubmitURL, null);
  513.             for (var i = 0; i < logins.length; i++) {
  514.               this._loginManager.removeLogin(logins[i]);
  515.             }
  516.  
  517.             var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
  518.                                                          Components.interfaces.nsILoginInfo,
  519.                                                          "init");
  520.             var extLoginInfo = new nsLoginInfo(hostname, formSubmitURL, null,
  521.                                                this._sessionKey, this._sessionSecret,
  522.                                                '' /*usernameField*/, '' /*passwordField*/);
  523.             this._loginManager.addLogin(extLoginInfo);
  524.           } else {
  525.             this._pwdServiceInt.addUserFull(PASSWORD_URL, this._sessionKey, this._sessionSecret,
  526.                                            'key', 'secret'); // last two values don't matter
  527.           }
  528.         }
  529.  
  530.         this._timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
  531.         this._timer.initWithCallback(this._checker, BASE_CHECK_INTERVAL/5, Ci.nsITimer.TYPE_REPEATING_SLACK);
  532.  
  533.         // fire off another thread to get things started
  534.         this._oneShotTimer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
  535.         this._oneShotTimer.initWithCallback(this._initialize, 1, Ci.nsITimer.TYPE_ONE_SHOT);
  536.  
  537.         this.checkCanSetStatus();
  538.     },
  539.     savePref: function( pref_name, pref_val ) {
  540.         this._prefService.unlockPref( pref_name );
  541.         this._prefService.setCharPref( pref_name, pref_val );
  542.         this._prefService.lockPref( pref_name );
  543.     },
  544.     sessionEnd: function() {
  545.         debug('sessionEnd');
  546.         // remove session info from prefs because of explicit logout
  547.         // or because they didn't work
  548.         this.savePref( 'extensions.facebook.uid', '' );
  549.         if (this._ff3Login) { // Clear out saved information for this extension
  550.           var hostname = PASSWORD_URL;
  551.           var formSubmitURL = PASSWORD_URL;
  552.  
  553.           var logins = this._loginManager.findLogins({}, hostname, formSubmitURL, null);
  554.           for (var i = 0; i < logins.length; i++) {
  555.             this._loginManager.removeLogin(logins[i]);
  556.           }
  557.         } else if (this._sessionKey && this._sessionSecret) {
  558.           debug('Removing sessionKey from passwords', this._sessionKey);
  559.           this._pwdService.removeUser(PASSWORD_URL, this._sessionKey);
  560.         }
  561.  
  562.         this.initValues();
  563.         if (this._timer) {
  564.           this._timer.cancel();
  565.           this._oneShotTimer.cancel();
  566.         }
  567.         this.notify(null, 'facebook-session-end', null);
  568.     },
  569.     hintPageLoad: function(fbPage) {
  570.         if (fbPage)
  571.             this._lastFBLoad = Date.now();
  572.         else
  573.             this._lastPageLoad = Date.now();
  574.     },
  575.     initValues: function() {
  576.         this._sessionKey    = null;
  577.         this._sessionSecret = null;
  578.         this._uid           = null;
  579.         this._loggedIn      = false;
  580.         this._canSetStatus  = null;
  581.         this._loggedInUser  = null;
  582.  
  583.         this._messages      = null; // CountedNotif
  584.         this._pokes         = null; // CountedNotif
  585.         this._groupInvs     = null; // SetNotif
  586.         this._eventInvs     = null; // SetNotif
  587.         this._reqs          = null; // SetNotif
  588.  
  589.         this._friendDict   = {};
  590.         this._albumDict    = {};
  591.  
  592.         this._pendingRequest = false;
  593.         this._pendingRequests = [];
  594.         this._lastCallId     = 0;
  595.         this._lastChecked    = 0;
  596.         this._lastFBLoad     = 0;
  597.         this._lastPageLoad   = 0;
  598.         this._lastCheckedFriends = 0;
  599.     },
  600.     setStatus: function(status) {
  601.         if (status == "is " || status == "set your status...") {
  602.             status = "";
  603.         }
  604.  
  605.         if (status == this._loggedInUser.status) {
  606.             return;
  607.         }
  608.  
  609.         if (this.canSetStatus) {
  610.             var is_clear = status=="";
  611.             var params   = is_clear ? ['clear=1'] : ['status='+status, 'status_includes_verb=1'];
  612.             fbSvc.callMethod('facebook.users.setStatus', params, function(data) {
  613.                 var result; var msg;
  614.                 debug('users.setStatus:', params, data);
  615.                 if (data) {
  616.                     msg = is_clear ? 'Your status was cleared successfully.'
  617.                      : 'Your status was set successfully.';
  618.                     result = is_clear ? 'clear' : 'set';
  619.                 } else {
  620.                     result = 'fail';
  621.                     msg = 'Your status could not be set.';
  622.                 }
  623.                 fbSvc.notify(null, 'facebook-status-set-result', result);
  624.                 fbSvc._showPopup('you.status', fbSvc._loggedInUser.pic_sq, msg,
  625.                    'http://www.facebook.com/profile.php?id=' + fbSvc._uid + '&src=fftb#status');
  626.             });
  627.         } else {
  628.             debug("Facebook Toolbar doesn't have status_update perm?");
  629.             fbSvc.notify(null, 'facebook-status-set-result', 'perm' );
  630.         }
  631.     },
  632.     checkCanSetStatus: function() {
  633.       if (null != this._canSetStatus) {return;}
  634.  
  635.       this.callMethod('facebook.users.hasAppPermission', ['ext_perm=status_update'], function(data){
  636.           vdebug('data:', data);
  637.  
  638.           fbSvc._canSetStatus = ('1' == data.toString());
  639.           debug('Can Set Status?', fbSvc._canSetStatus);
  640.       });
  641.     },
  642.     clearCanSetStatus: function() {
  643.         this._canSetStatus = null;
  644.     },
  645.  
  646.     // onInit : bool : true if this is an initial load of notifications, false otherwise
  647.     // interval: int : for non-initial loads of notifications, the window in which to
  648.     // grab facebook notifications
  649.     checkNotifications: function(onInit, window) {
  650.         this.callMethod('facebook.notifications.get', [], function(data) {
  651.             vdebug('notification data:', data);
  652.             if (onInit){
  653.                 fbSvc._messages = new CountedNotif( data.messages,'facebook-msgs-updated', fbSvc
  654.                     , function( msgCount ) {
  655.                         vdebug( "msgCount", msgCount );
  656.                         var text = 'You have ' + ( msgCount==1 ? 'a new message' : 'new messages.' );
  657.                         fbSvc.showPopup('you.msg', 'chrome://facebook/content/mail_request.gif',
  658.                                          text, 'http://www.facebook.com/inbox/');
  659.                     } );
  660.                 fbSvc._pokes = new CountedNotif( data.pokes, 'facebook-pokes-updated', fbSvc
  661.                     , function( pokeCount ) {
  662.                         vdebug( "pokeCount", pokeCount );
  663.                         if( pokeCount > 0 ) {
  664.                           var text = 'You have been ';
  665.                           if( 1 == pokeCount )
  666.                             text += 'poked.';
  667.                           else if( 4 >= pokeCount )
  668.                             text += 'poked ' + pokeCount + ' times.';
  669.                           else
  670.                             text += 'poked many times.';
  671.  
  672.                           fbSvc.showPopup('you.poke', 'chrome://facebook/content/poke.gif',
  673.                                           text, 'http://www.facebook.com/home.php');
  674.                         }
  675.                     } );
  676.                 fbSvc._groupInvs = new SetNotif(data.group_invites, 'facebook-group-invs-updated', fbSvc, null );
  677.                 fbSvc._eventInvs = new SetNotif(data.event_invites, 'facebook-event-invs-updated', fbSvc, null );
  678.                 fbSvc._reqs = new SetNotif(data.friend_requests, 'facebook-reqs-updated', fbSvc
  679.                     , function( self, delta ) {
  680.                         fbSvc.getUsersInfo(delta, function(users) {
  681.                             debug( "Got friend reqs", users.length );
  682.                             for each (var user in users) {
  683.                                 self.items[user.id] = user;
  684.                                 fbSvc.notify(user, 'facebook-new-req', user.id);
  685.                                 fbSvc.showPopup('you.req', user.pic_sq, user.name + ' wants to be your friend',
  686.                                                'http://www.facebook.com/reqs.php');
  687.                             }
  688.                         });
  689.                     });
  690.             } else {
  691.                 fbSvc._messages.update( data.messages );
  692.                 fbSvc._pokes.update( data.pokes );
  693.                 fbSvc._groupInvs.update( data.group_invites );
  694.                 fbSvc._eventInvs.update( data.event_invites );
  695.                 fbSvc._reqs.update( data.friend_requests );
  696.             }
  697.         });
  698.  
  699.         if (this._prefService.getBoolPref('extensions.facebook.notifications.toggle')
  700.             && this._prefService.getBoolPref('extensions.facebook.notifications.you.site')) {
  701.  
  702.           var notif_query = " SELECT title_text, body_text, href, app_id FROM notification "
  703.             + " WHERE recipient_id = :user AND is_hidden=0 AND is_unread=1";
  704.           if (!onInit) {
  705.             notif_query += " AND updated_time > (now() - :window)";
  706.             notif_query = notif_query
  707.               .replace( /:user/g, this._uid )
  708.               .replace( /:window/g, Math.ceil(window/1000) + 30 );
  709.           } else {
  710.             notif_query = notif_query.replace( /:user/g, this._uid );
  711.           }
  712.  
  713.           var app_query = " SELECT app_id, icon_url FROM application"
  714.            + " WHERE app_id IN (SELECT app_id FROM #notif_query)";
  715.  
  716.           var queries = {
  717.             notif_query: notif_query,
  718.             app_query: app_query
  719.           };
  720.           var queries_str = JSON.stringify(queries);
  721.  
  722.           this.callMethod('facebook.fql.multiquery', ['queries='+queries_str], function(data) {
  723.                             var application_icons = {};
  724.                             var app_result, notif_result;
  725.                             for each (var query_result in data) {
  726.                               if ('notif_query' == query_result.name) {
  727.                                 notif_result = query_result.fql_result_set;
  728.                               }
  729.                               if ('app_query' == query_result.name) {
  730.                                 app_result = query_result.fql_result_set;
  731.                               }
  732.                             }
  733.  
  734.                             for each (var app_info in app_result) {
  735.                               application_icons[app_info.app_id] = app_info.icon_url;
  736.                             }
  737.  
  738.                             for each (var notification in notif_result) {
  739.                               var notification_contents = notification.title_text;
  740.                               if (notification.body_text) {
  741.                                 notification_contents += "\n\n"
  742.                                   + '"' + notification.body_text + '"';
  743.                               }
  744.                               var app_icon = application_icons[notification.app_id]
  745.                                 || 'chrome://facebook/content/wall_post.gif';
  746.                               fbSvc.showPopup('you.site', app_icon,
  747.                                               notification_contents,
  748.                                               notification.href);
  749.                             }
  750.  
  751.                           });
  752.         }
  753.     },
  754.     parseUsers: function(user_data) {
  755.         users = {};
  756.         for each (var user in user_data) {
  757.             vdebug("user: " + user.uid, user);
  758.  
  759.             // note: for name and status, need to utf8 decode them using
  760.             // the decodeURIComponent(escape(s)) trick - thanks
  761.             // http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
  762.             var name   = user.name, // decodeURIComponent(escape(user.name)),
  763.             id     = String(user.uid),
  764.             status = user.status ? user.status.message // decodeURIComponent(escape(user.status.message))
  765.                                  : null,
  766.             stime  = user.status && user.status.time ? user.status.time : 0,
  767.             ptime  = Number(user.profile_update_time),
  768.             notes  = Number(user.notes_count),
  769.             wall   = Number(user.wall_count),
  770.             pic    = user.pic_small  ? String(decodeURI(user.pic_small)) : null,
  771.             pic_sq = user.pic_square ? String(decodeURI(user.pic_square)) : null
  772.             ;
  773.             if (!pic) {
  774.                 pic = pic_sq = 'chrome://facebook/content/t_default.jpg';
  775.             }
  776.             debug ("User status = "+status);
  777.             users[id] = new facebookUser(id, name, pic, pic_sq, status, stime, ptime, notes, wall);
  778.         }
  779.         return users;
  780.     },
  781.     checkAlbums: function(window) {
  782.       if( 0 == window ) { // initialization
  783.         debug("Initial album check...");
  784.         var query = " SELECT aid, owner, modified, size FROM album "
  785.           + " WHERE owner IN (SELECT uid2 FROM friend WHERE uid1 = :user) and size > 0;";
  786.         query = query.replace( /:user/g, this._uid );
  787.         this.callMethod('facebook.fql.query', ['query='+query], function(data) {
  788.           for each( var album in data ) {
  789.             var aid      = Number(album.aid),
  790.                 size     = Number(album.size),
  791.                 modified = Number(album.modified),
  792.                 owner    = Number(album.owner);
  793.             fbSvc._albumDict[ aid ] = { 'modified': modified,
  794.                                         'size': size,
  795.                                         'owner': owner };
  796.           }
  797.         });
  798.       }
  799.       // don't check for album changes if not going to show notifications
  800.       else if( this._prefService.getBoolPref('extensions.facebook.notifications.toggle') &&
  801.                this._prefService.getBoolPref('extensions.facebook.notifications.friend.album') ) {
  802.         debug("Album check...", window);
  803.         var query = " SELECT aid, owner, name, modified, size, link FROM album "
  804.           + " WHERE owner IN (SELECT uid2 FROM friend WHERE uid1 = :user )"
  805.          + " AND modified > (now() - :window) AND size > 0;";
  806.         query = query.replace( /:user/g, this._uid )
  807.                      .replace( /:window/g, Math.ceil(window/1000) + 30 ); // 30 sec of wiggle room
  808.         debug(query);
  809.         this.callMethod('facebook.fql.query', ['query='+query], function(data) {
  810.           for each( var album in data ) {
  811.             var aid      = Number(album.aid),
  812.                 size     = Number(album.size),
  813.                 modified = Number(album.modified),
  814.                 name     = String(album.name),
  815.                 link     = decodeURIComponent(escape(String(album.link))),
  816.                 owner    = Number(album.owner);
  817.             debug( "Modified album!", owner, name, modified, link );
  818.             var album_owner = fbSvc._friendDict[owner];
  819.             var pvs_album = fbSvc._albumDict[aid];
  820.             if( album_owner ) {
  821.               if( pvs_album ) { // album already existed
  822.                 if( size > pvs_album.size ) {
  823.                   fbSvc.showPopup( 'friend.album', 'chrome://facebook/skin/photo.gif',
  824.                                    album_owner.name + ' added new photos to "' + name + '"',
  825.                                    link + "&src=fftb" );
  826.                 }
  827.               }
  828.               else {
  829.                 fbSvc.showPopup( 'friend.album', 'chrome://facebook/skin/photo.gif',
  830.                                  album_owner.name + ' created the album "' + album.name + '"',
  831.                                  link + "&src=fftb" );
  832.               }
  833.               fbSvc._albumDict[aid] = { 'modified': modified,
  834.                                         'owner': owner,
  835.                                         'size': size };
  836.             }
  837.           }
  838.         });
  839.       }
  840.     },
  841.     checkUsers: function(onInit) {
  842.         var friendUpdate = false;
  843.         var query = ' SELECT uid, name, status, pic_small, pic_square, wall_count, notes_count, profile_update_time'
  844.                   + ' FROM user WHERE uid = :user '
  845.                   + ' OR uid IN (SELECT uid2 FROM friend WHERE uid1 = :user );';
  846.         query = query.replace( /:user/g, this._uid );
  847.         this.callMethod('facebook.fql.query', ['query='+query], function(data) {
  848.             fbSvc._lastCheckedFriends = Date.now();
  849.  
  850.             // update the friends in place for non-onInit cases
  851.             // because we don't care about removing the defriended ... otherwise we'd
  852.             // make a new friends array every time so that we handle losing friends properly
  853.             friendDict = fbSvc.parseUsers(data);
  854.  
  855.             var loggedInUser = friendDict[fbSvc._uid];
  856.             if (loggedInUser) {
  857.               debug("loggedInUser", loggedInUser.name);
  858.               delete friendDict[fbSvc._uid];
  859.             }
  860.  
  861.             // Check for user's info changes
  862.             if (fbSvc._loggedInUser) {
  863.                 if (fbSvc._loggedInUser.status != loggedInUser.status) {
  864.                     fbSvc.notify(null, 'facebook-status-updated', loggedInUser.status);
  865.                 }
  866.                 fbSvc._loggedInUser = loggedInUser;
  867.             } else if (loggedInUser) {
  868.                 fbSvc._loggedInUser = loggedInUser;
  869.                 fbSvc.notify(fbSvc._loggedInUser, 'facebook-session-start', fbSvc._loggedInUser.id);
  870.                 debug('logged in: howdy', fbSvc._loggedInUser.name);
  871.             } else {
  872.                 debug("no info for logged-in user", fbSvc._uid);
  873.             }
  874.             debug('check done with logged-in user');
  875.  
  876.             // Check for user's friends' info changes
  877.             for each (var friend in friendDict) {
  878.                 if (!onInit) {
  879.                     if (!fbSvc._friendDict[friend.id]) {
  880.                         fbSvc.notify(friend, 'facebook-new-friend', friend['id']);
  881.                         fbSvc.showPopup('you.friend', friend.pic_sq, friend.name + ' is now your friend',
  882.                         'http://www.facebook.com/profile.php?id=' + friend.id + '&src=fftb');
  883.                         fbSvc._friendCount++; // increment the count
  884.                         friendUpdate = true;
  885.                     } else {
  886.                         notifyProf = true; // only notify if not displaying another notification
  887.                         if (fbSvc._friendDict[friend.id].status != friend.status) {
  888.                             if (friend.status) {
  889.                                 if (fbSvc._friendDict[friend.id].stime &&
  890.                                     friend.stime < fbSvc._friendDict[friend.id].stime) {
  891.                                     // weed out bad data using timestamp comparisons ...
  892.                                     debug("stale status update?"
  893.                                           + " NEW: " + friend.stime + ": " + friend.status + " ;"
  894.                                           + " PVS: " + fbSvc._friendDict[friend.id].stime + ": "
  895.                                           + fbSvc._friendDict[friend.id].status );
  896.                                     // ... overwrite the bad data with previous known good data
  897.                                     friend.stime  = fbSvc._friendDict[friend.id].stime;
  898.                                     friend.status = fbSvc._friendDict[friend.id].status;
  899.                                 } else {
  900.                                     fbSvc.notify(friend, 'facebook-friend-updated', 'status');
  901.                                     notifyProf = !fbSvc.showPopup('friend.status', friend.pic_sq,
  902.                                         friend.name + ' ' + RenderStatusMsg(friend.status),
  903.                                         'http://www.facebook.com/profile.php?id=' + friend.id + '&src=fftb#status');
  904.                                 }
  905.                             } else {
  906.                                 fbSvc.notify(friend, 'facebook-friend-updated', 'status-delete');
  907.                             }
  908.                             friendUpdate = true;
  909.                         }
  910.                         if (fbSvc._friendDict[friend.id].wall < friend.wall) {
  911.                             fbSvc.notify(friend, 'facebook-friend-updated', 'wall');
  912.                             notifyProf = notifyProf && !fbSvc.showPopup('friend.wall', friend.pic_sq, 'Someone wrote on ' + friend.name + "'s wall",
  913.                             'http://www.facebook.com/profile.php?id=' + friend.id + '&src=fftb#wall');
  914.                             vdebug('wall count updated', fbSvc._friendDict[friend.id].wall, friend.wall);
  915.                         }
  916.                         if (fbSvc._friendDict[friend.id].notes < friend.notes) {
  917.                             fbSvc.notify(friend, 'facebook-friend-updated', 'notes');
  918.                             notifyProf = notifyProf && !fbSvc.showPopup('friend.note', friend.pic_sq, friend.name + ' wrote a note.',
  919.                               'http://www.facebook.com/notes.php?id=' + friend.id + '&src=fftb');
  920.                             vdebug('note count updated', fbSvc._friendDict[friend.id].notes, friend.notes);
  921.                         }
  922.                         if (fbSvc._friendDict[friend.id].ptime != friend.ptime && 0 != friend.ptime ) {
  923.                             fbSvc.notify(friend, 'facebook-friend-updated', 'profile');
  924.                             if (notifyProf) {
  925.                               fbSvc.showPopup('friend.profile', friend.pic_sq, friend.name + ' updated his/her profile',
  926.                               'http://www.facebook.com/profile.php?id=' + friend.id + '&src=fftb&highlight');
  927.                             }
  928.                             friendUpdate = true;
  929.                         }
  930.                     }
  931.                     fbSvc._friendDict[friend.id] = friend;
  932.                 }
  933.             }
  934.             if( onInit )
  935.               fbSvc._friendDict = friendDict;
  936.             if (onInit || friendUpdate) {
  937.                 debug('sending notification');
  938.                 fbSvc.notify(null, 'facebook-friends-updated', null);
  939.             }
  940.             debug('done checkUsers', friendUpdate);
  941.         });
  942.     },
  943.     getFriends: function(count) {
  944.         debug( "getFriends called!");
  945.         var friend_arr = [];
  946.         for each( var f in fbSvc._friendDict )
  947.           friend_arr.push( f );
  948.         count.value = friend_arr.length;
  949.         return friend_arr;
  950.     },
  951.     notify: function( subject, topic, data ){
  952.         debug( "notify", topic, data );
  953.         this._observerService.notifyObservers( subject, topic, data );
  954.     },
  955.     // deprecated: replaced by fql query in checkUsers
  956.     getUsersInfo: function(users, callback) {
  957.         this.callMethod('facebook.users.getInfo', ['users='+users.join(','),
  958.                         'fields=name,status,pic_small,pic_square,wall_count,notes_count,profile_update_time'],
  959.                         function(data) {
  960.             callback(fbSvc.parseUsers(data));
  961.         });
  962.     },
  963.     generateSig: function (params) {
  964.         var str = '';
  965.         params.sort();
  966.         for (var i = 0; i < params.length; i++) {
  967.             str += params[i];
  968.         }
  969.         str += this._sessionSecret;
  970.         return MD5(str);
  971.     },
  972.     /**
  973.      * Returns common parameters that will always be sent in any request.
  974.      * You should use this when calling the facebook server API yourself (i.e.
  975.      * not using callMethod()).
  976.      *
  977.      * @returns An object with parameter names and values as key and values
  978.      * respectively.
  979.      */
  980.     getCommonParams: function() {
  981.         var callId = Date.now();
  982.         if (callId <= this._lastCallId) {
  983.             callId = this._lastCallId + 1;
  984.         }
  985.         this._lastCallId = callId;
  986.         return {
  987.             'session_key': this._sessionKey,
  988.             'api_key': this._apiKey,
  989.             'v': '1.0',
  990.             'call_id': callId,
  991.             'format': 'json'
  992.         };
  993.     },
  994.     // Note that this is intended to call non-login related Facebook API
  995.     // functions - ie things other than facebook.auth.*.  The login-related
  996.     // calls are done in the chrome layer because they are in direct response to user actions.
  997.     // Also note that this is synchronous so you should not call it from the UI.
  998.     callMethod: function (method, params, callback, secondTry) {
  999.         if (!this._loggedIn) return null;
  1000.  
  1001.         var origParams = params.slice(0); // easy way to make a deep copy of the array
  1002.         params.push('method=' + method);
  1003.  
  1004.         for (let [name, value] in Iterator(this.getCommonParams())) {
  1005.             params.push(name + "=" + value);
  1006.         }
  1007.  
  1008.         params.push('sig=' + this.generateSig(params));
  1009.  
  1010.         var paramsEncoded = [];
  1011.         for each (var param in params) {
  1012.             var idx = param.indexOf("=");
  1013.             if (idx < 0) {
  1014.                 debug("Invalid parameter: " + param);
  1015.                 return;
  1016.             }
  1017.             var key = param.slice(0, idx);
  1018.             var value = param.slice(idx + 1);
  1019.             paramsEncoded.push(key + "=" + encodeURIComponent(value));
  1020.         }
  1021.         var message = paramsEncoded.join('&');
  1022.  
  1023.         try {
  1024.             // Yuck...xmlhttprequest doesn't always work so we have to do this
  1025.             // the hard way.  Thanks to Manish from Flock for the tip!
  1026.             var restserver = 'http://api.facebook.com/restserver.php';
  1027.             var channel = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService)
  1028.                                .newChannel(restserver, null, null)
  1029.                                .QueryInterface(Ci.nsIHttpChannel);
  1030.             var upStream = Cc['@mozilla.org/io/string-input-stream;1'].createInstance(Ci.nsIStringInputStream);
  1031.             upStream.setData(message, message.length);
  1032.             channel.QueryInterface(Ci.nsIUploadChannel)
  1033.                    .setUploadStream(upStream, "application/x-www-form-urlencoded", -1);
  1034.             channel.requestMethod = "POST";
  1035.             var listener = {
  1036.                 onDataAvailable: function(request, context, inputStream, offset, count) {
  1037.                     var sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
  1038.                     sis.init(inputStream);
  1039.                     this.resultTxt += sis.read(count);
  1040.                 },
  1041.                 onStartRequest: function(request, context) {
  1042.                     debug('starting request', method);
  1043.                     this.resultTxt = '';
  1044.                     if (fbSvc._pendingRequests.length) {
  1045.                         (fbSvc._pendingRequests.shift())();
  1046.                     } else {
  1047.                         fbSvc._pendingRequest = false;
  1048.                     }
  1049.                 },
  1050.                 onStopRequest: function(request, context, statusCode) {
  1051.                     if (statusCode == Components.results.NS_OK) {
  1052.                         var data = null;
  1053.                         // native JSON seems to have problems parsing
  1054.                         // primitives like "true", "1", etc. as of FF3.1b2
  1055.                         try {
  1056.                             data = JSON.parse(this.resultTxt);
  1057.                         } catch (e) {
  1058.                             try {
  1059.                                 this.resultTxt = this.resultTxt.trim()
  1060.                                 data = JSON.parse(this.resultTxt);
  1061.                             } catch (e) {
  1062.                                 vdebug("failed to parse: '" + this.resultTxt + "'");
  1063.                                 if (this.resultTxt == "true") {
  1064.                                    data = true;
  1065.                                 } else if (this.resultTxt == "false") {
  1066.                                    data = false;
  1067.                                 } else {
  1068.                                    data = Number(this.resultTxt);
  1069.                                    if (NaN == data) {
  1070.                                        if (!secondTry) {
  1071.                                            debug('TRYING ONE MORE TIME');
  1072.                                            fbSvc.callMethod(method, origParams, callback, true);
  1073.                                        }
  1074.                                        return;
  1075.                                    }
  1076.                                 }
  1077.                             }
  1078.                         }
  1079.  
  1080.                         if (typeof data.error_code != "undefined") {
  1081.                             if (data.error_code == 102) {
  1082.                                 debug('session expired, logging out.');
  1083.                                 fbSvc.sessionEnd();
  1084.                             } else if (data.error_code == 4) {
  1085.                                 // rate limit hit, let's just cancel this request, we'll try again soon enough.
  1086.                                 debug('RATE LIMIT ERROR');
  1087.                             } else {
  1088.                                 debug('API error:' + data.error_code);
  1089.                                 debug(JSON.stringify(data));
  1090.                                 if (!secondTry) {
  1091.                                     debug('TRYING ONE MORE TIME');
  1092.                                     fbSvc.callMethod(method, origParams, callback, true);
  1093.                                 }
  1094.                             }
  1095.                         } else {
  1096.                             callback(data);
  1097.                         }
  1098.                     }
  1099.                 }
  1100.             };
  1101.             if (this._pendingRequest) {
  1102.                 this._pendingRequests.push(function() {
  1103.                     channel.asyncOpen(listener, null);
  1104.                 });
  1105.             } else {
  1106.                 this._pendingRequest = true;
  1107.                 channel.asyncOpen(listener, null);
  1108.             }
  1109.         } catch (e) {
  1110.             debug('Exception sending REST request: ', e);
  1111.             return null;
  1112.         }
  1113.     },
  1114.     showPopup: function(type, pic, label, url) {
  1115.         if (!this._prefService.getBoolPref('extensions.facebook.notifications.toggle') ||
  1116.             !this._prefService.getBoolPref('extensions.facebook.notifications.' + type)) {
  1117.             return false;
  1118.         }
  1119.         return this._showPopup(type, pic, label, url);
  1120.     },
  1121.     _showPopup: function(type, pic, label, url) {
  1122.         debug('showPopup', type, pic, label, url);
  1123.         try {
  1124.             if (!this._alertService)
  1125.                 this._alertService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
  1126.             if (this._prefService.getBoolPref('extensions.facebook.notifications.growl')) {
  1127.                 var notifyTitle = this.stringBundle.GetStringFromName("notificationtitle");
  1128.                 if (url) {
  1129.                     this._alertService.showAlertNotification(pic, notifyTitle, label,
  1130.                                                              true, url, new AlertObserver() );
  1131.                 } else {
  1132.                     this._alertService.showAlertNotification(pic, notifyTitle, label);
  1133.                 }
  1134.                 return true;
  1135.             }
  1136.         } catch (e) {
  1137.             debug('caught', e);
  1138.         }
  1139.  
  1140.         // either native FF alerts are not available or they aren't being used
  1141.         this._numAlertsObj.value++;
  1142.         var win = Cc["@mozilla.org/appshell/appShellService;1"]
  1143.         .getService(Ci.nsIAppShellService).hiddenDOMWindow;
  1144.         var left = win.screen.width - 215;
  1145.         var top  = win.screen.height - 105*this._numAlertsObj.value;
  1146.         win.openDialog('chrome://facebook/content/notifier.xul', '_blank',
  1147.                        'chrome,titlebar=no,popup=yes,left=' + left + ',top=' + top + ',width=210,height=100',
  1148.                        pic, label, url, this._numAlertsObj);
  1149.         return true;
  1150.     }
  1151. };
  1152.  
  1153. // boilerplate stuff
  1154. var facebookFactory = {
  1155.     createInstance: function (aOuter, aIID) {
  1156.         debug('createInstance');
  1157.         if (aOuter != null) {
  1158.             throw Components.results.NS_ERROR_NO_AGGREGATION;
  1159.         }
  1160.         return (new facebookService()).QueryInterface (aIID);
  1161.     }
  1162. };
  1163. var facebookModule = {
  1164.     registerSelf: function (aCompMgr, aFileSpec, aLocation, aType) {
  1165.         debug('registerSelf');
  1166.         aCompMgr = aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1167.         aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType);
  1168.  
  1169.         // Adding app-startup category in order to get the final-ui-startup
  1170.         // notification.
  1171.         this.getCategoryManager().addCategoryEntry("app-startup", "FB-startup",
  1172.                                                    "service," + CONTRACT_ID, true, true);
  1173.     },
  1174.     unregisterSelf: function(aCompMgr, aLocation, aType) {
  1175.         debug('unregisterSelf');
  1176.         aCompMgr = aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1177.         aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation);
  1178.  
  1179.         this.getCategoryManager().deleteCategoryEntry("app-startup", "FB-startup", true);
  1180.     },
  1181.  
  1182.     getCategoryManager: function() {
  1183.       return Cc["@mozilla.org/categorymanager;1"].
  1184.                getService(Ci.nsICategoryManager);
  1185.     },
  1186.  
  1187.     getClassObject: function (aCompMgr, aCID, aIID) {
  1188.         debug('getClassObject');
  1189.         if (!aIID.equals (Ci.nsIFactory))
  1190.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  1191.  
  1192.         if (aCID.equals (CLASS_ID))
  1193.             return facebookFactory;
  1194.  
  1195.         throw Components.results.NS_ERROR_NO_INTERFACE;
  1196.     },
  1197.     canUnload: function(compMgr) {
  1198.         debug('canUnload');
  1199.         return true;
  1200.     }
  1201. };
  1202. function NSGetModule(compMgr, fileSpec) {
  1203.     debug('NSGetModule');
  1204.     return facebookModule;
  1205. }
  1206.  
  1207. function facebookUser(id, name, pic, pic_sq, status, stime, ptime, notes, wall) {
  1208.     this.id     = id;
  1209.     this.name   = name;
  1210.     this.pic    = pic;
  1211.     this.pic_sq = pic_sq;
  1212.     this.status = status;
  1213.     this.stime  = stime;
  1214.     this.ptime  = ptime;
  1215.     this.notes  = notes;
  1216.     this.wall   = wall;
  1217. }
  1218. facebookUser.prototype = {
  1219.     // nsISupports implementation
  1220.     QueryInterface: function (iid) {
  1221.         if (!iid.equals(Ci.fbIFacebookUser) &&
  1222.             !iid.equals(Ci.nsISupports))
  1223.             throw Components.results.NS_ERROR_NO_INTERFACE;
  1224.         return this;
  1225.     }
  1226. };
  1227.  
  1228. // just copied from lib.js, lame but i don't feel like including the whole
  1229. // file in here for this one function.
  1230. function RenderStatusMsg(msg) {
  1231.     msg = msg.replace(/\s*$/g, '');
  1232.     if (msg && '.?!\'"'.indexOf(msg[msg.length-1]) == -1) {
  1233.         msg = msg.concat('.');
  1234.     }
  1235.     return msg;
  1236. }
  1237.  
  1238. debug('loaded facebook.js');
  1239.